
#include <stdint.h>
#include <string.h>
#include <strings.h>
#include <netinet/in.h>

// Macros for recasting chars to longs and shorts
#define LONG  * (uint32_t*) &
#define SHORT * (uint16_t*) &

typedef struct {
 
  uint8_t header[20];
  int totalLength;
  int ttl;
  int protocol;
  short checksum;
  uint8_t srcIP[4];
  uint8_t destIP[4];
  int version;
  int headerLen;

} ipHeader;

void parseIP( ipHeader * h ) {

  uint8_t * header = h->header;
  h->version = header[0] >> 4;
  h->headerLen = header[0] & 0x0f;
  h->totalLength = ntohs( SHORT header[2] );
  h->ttl = header[8];
  h->protocol = header[9];
  h->checksum = ntohs( SHORT header[10]);
  memcpy ( h->srcIP, &header[12], 4 );
  memcpy ( h->destIP, &header[16], 4);


}

void constructIP( ipHeader * h, uint8_t src[4], uint8_t dest[4],
		  uint16_t totalLength, uint8_t ttl, uint8_t protocol) {

  // Clear out the header
  memset( h->header, 0, 20 );

  uint8_t * header = h->header;

  // Set the version to 4 and header length to 5 (20 bytes length)
  // Desired value: 0100 0101
  header[0] |= 1 << 0;
  header[0] |= 1 << 2;
  header[0] |= 1 << 6;

  // Differentiated services and congestion should be 0
  
  // Set the total length header
  h->totalLength = totalLength;
  SHORT header[2] = htons( h->totalLength );

  // ID, Flags, and fragmentation can be 0
  // Fragmentation flag should actually be 2
  header[6] |= 1 << 6;

  // Set the ttl
  h->ttl = ttl;
  header[8] = h->ttl;
  // Set the protocol
  h->protocol = protocol;
  header[9] = h->protocol;
  // Set the checksum
  // SHORT header[10] = htons (h->checksum);
  
  // Set the source and dest IPs
  memcpy( &header[12], src, 4 );
  memcpy( h->srcIP, src, 4 );
  memcpy( &header[16], dest, 4 );
  memcpy( h->destIP, dest, 4);

  //  LONG header[12] = htonl( h->srcIP );
  //  LONG header[16] = htonl( h->destIP );
 
}


typedef struct {

  uint8_t data[8];

  char type;
  char code;
  short checksum;
  short first;
  short second;

} icmpHeader;

void parseICMP( icmpHeader * h ) {
  
  uint8_t * header = h->data;
  h->type = header[0];
  h->code = header[1];
  h->checksum = ntohs( SHORT header[2]);
  h->first = ntohs( SHORT  header[4] );
  h->second = ntohs( SHORT header[6] );

}



void icmpError( icmpHeader * h ) {

  uint8_t * header = h->data;
  header[0] = 3;
  header[1] = h->code;
  header[2] = 0;
  header[3] = 0;
  // TODO - compute checksum and place it in 2-3
  header[4] = 0;
  header[5] = 0;
  header[6] = 0;
  header[7] = 0;

}

void icmpPingReply( icmpHeader * h ) {

  uint8_t * header = h->data;
  header[0] = 0; // Type
  header[1] = 0; // Code
  // TODO - set to 0, compute checksum, and place it here
  // header[2] = ...
  
  // Rest of packet is not modified, since sender is depending on it
  
}

void icmpTTLReply ( icmpHeader * h ) {

  uint8_t * header = h->data;
  header[0] = 11;
  header[1] = 0;
  // TODO - set to 0, compute checksum, and place it here
  //header[2] = htons( (uint16_t) h->checksum );
  header[4] = 0;
  header[5] = 0;
  header[6] = 0;
  header[7] = 0;

}

void icmpDestinationUnreachable( icmpHeader * h ) {
  h->code = 0;
  icmpError(h);

}


void icmpProtocolUnreachable( icmpHeader * h ) {

  h->code = 2;
  icmpError(h);

}

void icmpHostUnreachable( icmpHeader * h ) {

  h->code = 1;
  icmpError(h);

}


typedef struct {

  uint8_t data[28];

  // TODO - sizes for MAC addresses?
  uint8_t senderAddr[6];
  uint8_t senderIP[4];
  uint8_t receiverAddr[6];
  uint8_t receiverIP[4];
  uint16_t operation;

} arpHeader;

void arpResponse( arpHeader * h, 
		  uint8_t srcMac[6], uint8_t destMac[6],
		  uint8_t srcIP[4], uint8_t destIP[4] ) {

  memset( h->data, 0, 28 );
  
  memcpy( h->senderAddr, srcMac, 6 );
  memcpy( h->receiverAddr, destMac, 6 );
  memcpy( h->senderIP, srcIP, 4 );
  memcpy( h->receiverIP, destIP, 4 );
  h->operation = 2;
  
  uint8_t * header = h->data;
  SHORT header[0] = htons( 1 );
  SHORT header[2] = htons( 0x0800 );
  header[4] = 6;
  header[5] = 4;
  SHORT header[6] = htons( 2 );
  //TODO - network order for MAC addresses?
  memcpy( &header[8], h->senderAddr, 6 );
  memcpy( &header[18], h->receiverAddr, 6 );
  memcpy( &header[14], h->senderIP, 4 );
  memcpy( &header[24], h->receiverIP, 4 );

}

void arpRequest( arpHeader * h, uint8_t srcMac[6],
		 uint8_t srcIP[4], uint8_t destIP[4]) {


  memset( h->data, 0, 28 );
  
  uint8_t * header = h->data;
  SHORT header[0] = htons( 1 );
  SHORT header[2] = htons( 0x0800 );
  header[4] = 6;
  header[5] = 4;
  SHORT header[6] = htons( 1 );
  h->operation = 1;
  memcpy( h->senderAddr, srcMac, 6 );
  memcpy( &header[8], srcMac, 6 );
  uint8_t destMac[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
  memcpy( h->receiverAddr, destMac, 6 );
  memcpy( &header[18], h->receiverAddr, 6 );
  memcpy( h->senderIP, srcIP, 4 );
  memcpy( h->receiverIP, destIP, 4 );
  memcpy( &header[14], h->senderIP, 4 );
  memcpy( &header[24], h->receiverIP, 4 );


}

void parseArp( arpHeader * h ) {

  uint8_t * header = h->data;
  h->operation = ntohs( SHORT header[6] );
  memcpy( h->senderAddr, &header[8], 6 );
  memcpy( h->receiverAddr, &header[18], 6 );
  memcpy( h->senderIP, &header[14], 4 );
  memcpy( h->receiverIP, &header[24], 4 );

}



typedef struct {

  uint8_t data[14];

  uint8_t dest[6];
  uint8_t src[6];
  uint8_t ip_flag;
  uint8_t arp_flag;


} ethernetHeader;


void parseEthernet ( ethernetHeader * h ) {

  memcpy( h->src, &h->data[0], 6 );
  memcpy( h->dest, &h->data[6], 6 );
  h->arp_flag = 0;
  h->ip_flag = 0;
  uint8_t arp[2] = {0x08, 0x06};
  uint8_t ip[2] = {0x08, 0x00};

  if ( ! bcmp( &h->data[12], arp, 2 ) ) {
    h->arp_flag = 1;
    h->ip_flag = 0;
  }
  if ( ! bcmp( &h->data[12], ip, 2 ) ) {
    h->arp_flag = 0;
    h->ip_flag = 1;
  }

  
  
  

}

void constructEthernet ( ethernetHeader * h, 
			 uint8_t src[6], uint8_t dest[6], 
			 short protocol ) {

  // Fill in the header struct
  memcpy( h->src, src, 6 );
  memcpy( h->dest, dest, 6 );
  
  // And... construct the header
  memcpy( &h->data[0], dest, 6 );
  memcpy( &h->data[6], src, 6 );
  SHORT h->data[12] =  htons(protocol);
  
}

void constructEthernetArp( ethernetHeader * h,
			   uint8_t src[6], uint8_t dest[6] ) {
  constructEthernet( h, src, dest, 0x0806 );
  h->arp_flag = 1;
  h->ip_flag = 0;

}

void constructEthernetIP( ethernetHeader * h,
			   uint8_t src[6], uint8_t dest[6] ) {
  constructEthernet( h, src, dest, 0x0800 );
  h->arp_flag = 0;
  h->ip_flag = 1;

}

			   
